iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0
Modern Web

擁抱 .Net Core系列 第 28

[Day28] 授權 - Authorized - 2

  • 分享至 

  • xImage
  •  

IAuthorizeService

TokenController.cs

[HttpGet]
public async Task<string> Get()
{
    var authorizeService = HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
    var authorizationResult = await authorizeService.AuthorizeAsync(HttpContext.User, null, new List<IAuthorizationRequirement>()
    {
        new RolesAuthorizationRequirement(new[] { "Admin" })
    });

    if(authorizationResult.Succeeded) return "Hello World";
    Response.StatusCode = 403;
    return "Forbidden";
}

昨天我們第一版的授權是透過IAuthorizationService.AuthorizeAsync 來做授權的
顧名思義,這個Service主要是做跟授權相關的事

public interface IAuthorizationService
{
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, IEnumerable<IAuthorizationRequirement> requirements);
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName);
}

提供了兩種授權方式

  1. IAuthorizationRequirement 為基準的授權方式
  2. Policy 為基準的授權方式

ClaimePrincipal 是正如我們傳入的HttpContext.User 代表的是使用者的身分

跟驗證類似,在授權完後會回傳一個Result: AuthorizationResult
一個非常單純的類別,只存驗證是否成功以及失敗的資訊
比較有趣的是不論成功或失敗只能夠透過靜態工廠方法產生該類別

public class AuthorizationResult
{
        private AuthorizationResult() { }
        public bool Succeeded { get; private set; }
        public AuthorizationFailure? Failure { get; private set; }
        public static AuthorizationResult Success() => new AuthorizationResult { Succeeded = true };
        public static AuthorizationResult Failed(AuthorizationFailure failure) => new AuthorizationResult { Failure = failure };
       
        public static AuthorizationResult Failed() => new AuthorizationResult { Failure = AuthorizationFailure.ExplicitFail() };
}

IAuthoriztionRequirement

我們昨天有提到的RolesAuthorizationRequirement 就是一種IAuthoriztionRequirement
表示需要取得授權的資格,以昨天的例子來看需要有ClaimType.RoleAdmin 的User才有辦法成功取得授權

IAuthoriztionRequirement 本身是個空介面,只是作為介面約束

public interface IAuthorizationRequirement
{
}

IAuthorizationHandler

RolesAuthorizationRequirement 除了實作IAuthoriztionRequirement
還繼承了 AuthorizationHandler<TIAuthoriztionRequirement> 這個實作了 IAuthorizationHandler 的類別

IAuthorizationHandler.cs

public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

我覺得驗證跟授權的設計概念頗相似,
授權一樣會透過Handler去處理 授權所需的上下文
且這一切都是透過IAuthorizeService的實作所完成

有幾個比較常見的IAuthoriztionRequirement

  • ClaimsAuthorizationRequirement
  • DenyAnonymousAuthorizationRequirement
  • NameAuthorizationRequirement
  • RolesAuthorizationRequirement

ClaimsAuthorizationRequirement

這個物件非常的單純暴力
就是檢查要求的ClaimsType中有沒有對應的值
Ex.

var claimsAuthorizationRequirement = new ClaimsAuthorizationRequirement("my-claim", new[] { "Eric" });

帶帶入的ClaimIdentity中有 ClaimType 為 my-claim , 且值為Eric 才能成功取得資源

ClaimsAuthorizationRequirement

public class ClaimsAuthorizationRequirement : AuthorizationHandler<ClaimsAuthorizationRequirement>, IAuthorizationRequirement
{
public ClaimsAuthorizationRequirement(string claimType, IEnumerable<string>? allowedValues)
{
    if (claimType == null)
    {
        throw new ArgumentNullException(nameof(claimType));
    }

    ClaimType = claimType;
    AllowedValues = allowedValues;
}
public string ClaimType { get; }
public IEnumerable<string>? AllowedValues { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimsAuthorizationRequirement requirement)
{
    if (context.User != null)
    {
        var found = false;
        if (requirement.AllowedValues == null || !requirement.AllowedValues.Any())
        {
            found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase));
        }
        else
        {
            found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)
                                                && requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal));
        }
        if (found)
        {
            context.Succeed(requirement);
        }
    }
    return Task.CompletedTask;
}
}

DenyAnonymousAuthorizationRequirement

這個物件所阻擋的是未經驗證使用者
也就是只要所有驗證方式(jwt, basic, cookie...)都不符合時就會被阻擋

NameAuthorizationRequirement

根據使用者的ClaimsIdenties 中是否有Name為對應的Name以取得授權

public class NameAuthorizationRequirement : AuthorizationHandler<NameAuthorizationRequirement>, IAuthorizationRequirement
{
    public NameAuthorizationRequirement(string requiredName)
    {
        if (requiredName == null)
        {
            throw new ArgumentNullException(nameof(requiredName));
        }

        RequiredName = requiredName;
    }

    public string RequiredName { get; }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NameAuthorizationRequirement requirement)
    {
        if (context.User != null)
        {
            if (context.User.Identities.Any(i => string.Equals(i.Name, requirement.RequiredName, StringComparison.Ordinal)))
            {
                context.Succeed(requirement);
            }
        }
        return Task.CompletedTask;
    }
}

RolesAuthorizationRequirement

RolesAuthorizationRequirement 一樣是去檢查ClaimIndentity中的Role中是否有滿足的Role

public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
{
public IEnumerable<string> AllowedRoles { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
    {
    if (context.User != null)
    {
        bool found = false;
        if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
        {
            // Review: What do we want to do here?  No roles requested is auto success?
        }
        else
        {
            found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
        }
        if (found)
        {
            context.Succeed(requirement);
        }
    }
    return Task.CompletedTask;
    }
}

上一篇
[Day27] 授權 - Authorized - 1
下一篇
[Day29] 靜態檔案,StaticFile
系列文
擁抱 .Net Core30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言